﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Globalization;
using Actya.Core.Infrastructure;

namespace Actya.Web.Infrastructure
{
	/// <summary>
	/// Themable Razor view engine for CMS purposes.
	/// Most of the code is from http://pietschsoft.com/post/2009/03/ASPNET-MVC-Implement-Theme-Folders-using-a-Custom-ViewEngine.aspx.
	/// </summary>
	public class CmsViewEngine : RazorViewEngine
	{
		private static readonly string[] _emptyLocations = new string[0];

		public CmsViewEngine()
		{
			// Search paths for the views
			// 0 = view name
			// 1 = controller name
			// 2 = theme name
			// 3 = cms content root
			base.ViewLocationFormats = new[]
                                       {
                                            "{3}/Themes/{2}/Views/{1}/{0}.cshtml",
                                            "{3}/Themes/{2}/Views/Shared/{0}.cshtml",
											"{3}/Themes/Default/Views/{1}/{0}.cshtml",
											"{3}/Themes/Default/Views/Shared/{0}.cshtml"
                                       };
			base.MasterLocationFormats = base.ViewLocationFormats;
			base.PartialViewLocationFormats = base.ViewLocationFormats;
		}

		public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
		{
			string[] viewLocationsSearched;
			string[] masterLocationsSearched;

			if (controllerContext == null)
			{
				throw new ArgumentNullException("controllerContext");
			}
			if (string.IsNullOrEmpty(viewName))
			{
				throw new ArgumentException("viewName must be specified.", "viewName");
			}

			string themeName = this.GetThemeToUse(controllerContext);

			string controllerName = controllerContext.RouteData.GetRequiredString("controller");

			string viewPath = this.GetPath(controllerContext, this.ViewLocationFormats, "ViewLocationFormats",
					viewName, themeName, controllerName, "View", useCache, out viewLocationsSearched);

			string masterPath = this.GetPath(controllerContext, this.MasterLocationFormats, "MasterLocationFormats",
					masterName, themeName, controllerName, "Master", useCache, out masterLocationsSearched);

			if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName)))
			{
				return new ViewEngineResult(this.CreateView(controllerContext, viewPath, masterPath), this);
			}
			return new ViewEngineResult(viewLocationsSearched.Union<string>(masterLocationsSearched));
		}

		public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
		{
			string[] strArray;
			if (controllerContext == null)
			{
				throw new ArgumentNullException("controllerContext");
			}
			if (string.IsNullOrEmpty(partialViewName))
			{
				throw new ArgumentException("partialViewName must be specified.", "partialViewName");
			}

			string themeName = this.GetThemeToUse(controllerContext);

			string controllerName = controllerContext.RouteData.GetRequiredString("controller");
			string partialViewPath = this.GetPath(controllerContext, this.PartialViewLocationFormats,
					"PartialViewLocationFormats", partialViewName, themeName, controllerName, "Partial", useCache, out strArray);
			if (string.IsNullOrEmpty(partialViewPath))
			{
				return new ViewEngineResult(strArray);
			}
			var result = new ViewEngineResult(CreatePartialView(controllerContext, partialViewPath), this);
			return result;

		}

		protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
		{
			try
			{
				return System.IO.File.Exists(controllerContext.HttpContext.Server.MapPath(virtualPath));
			}
			catch (HttpException exception)
			{
				if (exception.GetHttpCode() != 0x194)
				{
					throw;
				}
				return false;
			}
			catch
			{
				return false;
			}
		}

		private string GetThemeToUse(ControllerContext controllerContext)
		{
			var themeName = controllerContext.RouteData.Values["theme"] as string ?? "Default";
			return themeName;
		}

		private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName,
			string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
		{
			searchedLocations = _emptyLocations;
			if (string.IsNullOrEmpty(name))
			{
				return string.Empty;
			}
			if ((locations == null) || (locations.Length == 0))
			{
				throw new InvalidOperationException("locations must not be null or emtpy.");
			}

			bool flag = IsSpecificPath(name);
			string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
			if (useCache)
			{
				string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
				if (viewLocation != null)
				{
					return viewLocation;
				}
			}
			if (!flag)
			{
				return this.GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations);
			}
			return this.GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
		}

		private static bool IsSpecificPath(string name)
		{
			char ch = name[0];
			if (ch != '~')
			{
				return (ch == '/');
			}
			return true;
		}

		private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
		{
			return string.Format(CultureInfo.InvariantCulture,
				":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}",
				new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName });
		}

		private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name,
			string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
		{
			string virtualPath = string.Empty;
			searchedLocations = new string[locations.Length];
			var rootVirtualDir = "~/CmsContent"; // Default
			for (int i = 0; i < locations.Length; i++)
			{
				string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, themeName, rootVirtualDir });

				if (this.FileExists(controllerContext, str2))
				{
					searchedLocations = _emptyLocations;
					virtualPath = str2;
					this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
					return virtualPath;
				}
				searchedLocations[i] = str2;
			}
			return virtualPath;
		}

		private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
		{
			string virtualPath = name;
			if (!this.FileExists(controllerContext, name))
			{
				virtualPath = string.Empty;
				searchedLocations = new string[] { name };
			}
			this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
			return virtualPath;
		}
	}
}